/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.utils;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.BeginRequestMessage;
import cn.easyplatform.messages.vos.AbstractPageVo;
import cn.easyplatform.messages.vos.MeshTaskVo;
import cn.easyplatform.messages.vos.PageVo;
import cn.easyplatform.messages.vos.TaskVo;
import cn.easyplatform.spi.service.TaskService;
import cn.easyplatform.type.*;
import cn.easyplatform.web.ext.zul.ComboboxExt;
import cn.easyplatform.web.ext.zul.ListboxExt;
import cn.easyplatform.web.ext.zul.TreeExt;
import cn.easyplatform.web.layout.IMainTaskBuilder;
import cn.easyplatform.web.layout.LayoutManagerFactory;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.MainTaskSupport;
import cn.easyplatform.web.task.OperableHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.util.resource.Labels;
import org.zkoss.web.servlet.Servlets;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Treeitem;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;


/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public final class ExtUtils {

    private final static Logger log = LoggerFactory.getLogger(ExtUtils.class);

    private final static ObjectMapper mapper = new ObjectMapper();

    private final static ComboboxExt op = new ComboboxExt();

    private final static ComboboxExt rp = new ComboboxExt();

    private final static String[] ICONS = {"z-icon-briefcase", "z-icon-table",
            "z-icon-truck", "z-icon-money ", "z-icon-suitcase",
            "z-icon-calendar-o", "z-icon-bullseye", "z-icon-umbrella",
            "z-icon-sitemap", "z-icon-coffee", "z-icon-fighter-jet",
            "z-icon-beer", "z-icon-tachometer", "z-icon-flag-checkered",
            "z-icon-th-large", "z-icon-bitbucket", "z-icon-foursquare",
            "z-icon-dropbox", "z-icon-gear"};

    static {
        // op
        Comboitem ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.eq"));
        ci.setValue("=");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.ne"));
        ci.setValue("<>");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.li"));
        ci.setValue("like");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.ls"));
        ci.setValue("ls");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.lf"));
        ci.setValue("lf");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.between"));
        ci.setValue("between");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.nbetween"));
        ci.setValue("not between");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.in"));
        ci.setValue("in");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.nin"));
        ci.setValue("not in");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.gt"));
        ci.setValue(">");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.lt"));
        ci.setValue("<");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.ge"));
        ci.setValue(">=");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.le"));
        ci.setValue("<=");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.null"));
        ci.setValue("is null");
        ci.setParent(op);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.op.nnull"));
        ci.setValue("is not null");
        ci.setParent(op);

        op.setReadonly(true);
        op.setSelectedIndex(0);

        // rp
        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.rp.and"));
        ci.setValue("and");
        ci.setParent(rp);

        ci = new Comboitem();
        ci.setLabel(Labels.getLabel("datalist.query.rp.or"));
        ci.setValue("or");
        ci.setParent(rp);
        rp.setReadonly(true);
        rp.setSelectedIndex(0);
    }

    public static ComboboxExt getOp() {
        return (ComboboxExt) op.clone();
    }

    public static ComboboxExt getRp() {
        return (ComboboxExt) rp.clone();
    }

    public static <T> Comparator<T> createSortComparator(
            final boolean isAscending, final int index) {
        Comparator<T> comparator = new Comparator<T>() {
            @SuppressWarnings("unchecked")
            public int compare(T o1, T o2) {
                ListRowVo lrv1 = null;
                ListRowVo lrv2 = null;
                if (o1 instanceof Listitem) {
                    lrv1 = ((Listitem) o1).getValue();
                    lrv2 = ((Listitem) o2).getValue();
                } else {
                    lrv1 = ((Treeitem) o1).getValue();
                    lrv2 = ((Treeitem) o2).getValue();
                }
                if (lrv1 == null || lrv2 == null)
                    return 0;
                Comparable<Object> v1 = (Comparable<Object>) lrv1.getData()[index];
                Comparable<Object> v2 = (Comparable<Object>) lrv2.getData()[index];
                if (isAscending) {
                    if (v2 != null && v1 != null)
                        return v2.compareTo(v1);
                    else if (v2 != null)
                        return 1;
                    else if (v1 != null)
                        return -1;
                    else
                        return 0;
                } else {
                    if (v1 != null && v2 != null)
                        return v1.compareTo(v2);
                    else if (v1 != null)
                        return 1;
                    else if (v2 != null)
                        return -1;
                    else
                        return 0;
                }
            }
        };
        return comparator;
    }

    public static <T> Comparator<T> createEmptyComparator() {
        Comparator<T> comparator = new Comparator<T>() {
            public int compare(T o1, T o2) {
                return 0;
            }
        };
        return comparator;
    }

    public static void print(Component comp, String uri, String cssuri) {
        if (Strings.isBlank(uri))
            uri = Executions.encodeURL("~./js/bootstrap/template.zul");
        String script = "zk.print('" + comp.getUuid() + "', '" + uri + "'";
        if (cssuri != null) {
            if (uri.contains("zkau") && !cssuri.startsWith("/"))
                cssuri = "/" + cssuri;
            script += ", '" + cssuri + "');";
        } else {
            script += ");";
        }
        Clients.evalJavaScript(script);
    }

    public final static String getDeviceType(HttpServletRequest req) {
        String userAgent = Servlets.getUserAgent(req).toLowerCase();
        boolean isIPad = userAgent.contains("ipad");
        boolean isIphone = userAgent.contains("iphone os");
        boolean isAndroid = userAgent.contains("android");
        boolean isWM = userAgent.contains("windows mobile");
        if (isIPad || isIphone || isAndroid || isWM)
            return DeviceType.MOBILE.getName();
        userAgent = req.getHeader("x-requested-with");
        if (userAgent != null) {
            userAgent = userAgent.toLowerCase();
            isIPad = userAgent.contains("ipad");
            isIphone = userAgent.contains("iphone os");
            isAndroid = userAgent.contains("android");
            isWM = userAgent.contains("windows mobile");
            if (isIPad || isIphone || isAndroid || isWM)
                return DeviceType.MOBILE.getName();
        }
        return DeviceType.AJAX.getName();
    }

    public final static String getIconSclass() {
        Random rand = new Random();
        return ICONS[rand.nextInt(ICONS.length - 1)];
    }

    /**
     * 执行功能
     *
     * @param main
     * @param mesh
     * @param args
     */
    public final static void go(OperableHandler main, Component mesh, Object... args) {
        // go(taskId,openModel,code|cid,code);
        // openModel == OPEN_EMMBED时第3个参数是容器的id,
        // 否则如果长度大于1表示弹出窗口的位置,参考window.setPosition方法
        String taskId = (String) args[0];
        int openModel = Constants.OPEN_MODAL;
        String code = null;
        // 强制运行唯一功能
        boolean unique = false;
        if (args.length > 1) {
            if (args[1] instanceof Boolean)
                unique = (Boolean) args[1];
            else
                openModel = Nums.toInt(args[1], Constants.OPEN_MODAL);
        }
        String cid = null;
        if (args.length > 2) {
            if (args[2] instanceof String) {
                cid = (String) args[2];
                if (cid.length() == 1) {
                    code = cid;
                    cid = null;
                }
            } else if (args[2] instanceof Boolean)
                unique = (Boolean) args[2];
        }
        if (args.length > 3) {
            if (args[3] instanceof String)
                code = (String) args[3];
            else if (args[3] instanceof Boolean)
                unique = (Boolean) args[3];
        }
        if (args.length > 4 && args[4] instanceof Boolean)
            unique = (Boolean) args[4];
        Component container = null;
        if (openModel == Constants.OPEN_EMBBED && cid != null) {
            container = main.getComponent()
                    .getFellowIfAny(cid);
            if (container == null)
                openModel = Constants.OPEN_MODAL;
        }
        if (container == null)
            container = main.getContainer();
        TaskInfo taskInfo = new TaskInfo(taskId);
        TaskVo tv = new MeshTaskVo(taskId);
        FieldVo[] data = null;
        if (mesh instanceof TreeExt)
            data = ((TreeExt) mesh).getRawValue();
        else
            data = ((ListboxExt) mesh).getRawValue();
        tv.setVariables(Arrays.asList(data));
        // 只有打开方式不是EMBBED的功能
        if (openModel != Constants.OPEN_EMBBED || unique) {
            int c = WebUtils.checkTask(taskInfo, container);
            if (c == 1 || c == 0) {// 已经在运行
                if (c == 1)
                    throw new EasyPlatformWithLabelKeyException(
                            "main.task.has.run", taskId);
                return;
            }
        }
        if (openModel == Constants.OPEN_EMBBED)
            tv.setCid(cid);
        tv.setOpenModel(openModel);
        if (code != null)
            tv.setProcessCode(code);
        TaskService mtc = ServiceLocator
                .lookup(TaskService.class);
        BeginRequestMessage req = new BeginRequestMessage(tv);
        req.setId(main.getId());
        IResponseMessage<?> resp = mtc.begin(req);
        if (resp.isSuccess()) {
            if (resp.getBody() != null
                    && resp.getBody() instanceof AbstractPageVo) {
                MainTaskSupport builder = null;
                try {
                    AbstractPageVo pv = (AbstractPageVo) resp.getBody();
                    if (pv instanceof PageVo)
                        ((PageVo) pv).setPosition(cid);
                    builder = (MainTaskSupport) LayoutManagerFactory
                            .createLayoutManager()
                            .getMainTaskBuilder(container,
                                    tv.getId(), pv);
                    ((IMainTaskBuilder) builder).build();
                    taskInfo.setId(pv.getId());
                    taskInfo.setTitle(pv.getTitile());
                    taskInfo.setImage(pv.getImage());
                    if (openModel == Constants.OPEN_EMBBED) {
                        ((MainTaskSupport) main).appendChild(cid, builder);
                        taskInfo.setParentId(main.getId());
                    } else {
                        ((IMainTaskBuilder) builder).setHost(main);
                    }
                    WebUtils.registryTask(taskInfo);
                } catch (EasyPlatformWithLabelKeyException ex) {
                    builder.close(false);
                    throw ex;
                } catch (BackendException ex) {
                    builder.close(false);
                    throw ex;
                } catch (Exception ex) {
                    if (log.isErrorEnabled())
                        log.error("go", ex);
                    builder.close(false);
                    throw Lang.wrapThrow(ex);
                }
            }
        } else {
            if (openModel == Constants.OPEN_EMBBED && container != null)
                container.getChildren().clear();
            throw new BackendException(resp);
        }
    }

    public final static Object fromJson(String json) {
        try {
            Object obj = mapper.readValue(json, Object.class);
            return obj;
        } catch (IOException ex) {
            throw Lang.wrapThrow(ex);
        }
    }

    public final static String toJson(Object obj) {
        if (obj == null)
            return "";
        if (obj instanceof FieldVo[]) {
            Map<String, Object> map = new HashMap<String, Object>();
            FieldVo[] args = (FieldVo[]) obj;
            for (FieldVo field : args)
                map.put(field.getName(), field.getValue());
            obj = map;
        }
        try {
            return mapper.writeValueAsString(obj);
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
    }

    public final static String calculateInterval(long uptime) {
        if (uptime == 0) {
            return "0" + Labels.getLabel("admin.jvm.minute");
        }
        StringBuilder str = new StringBuilder();
        long p = Math.abs(uptime);
        long day = p / (24 * 3600000);
        if (day > 0) {
            str.append(day).append(Labels.getLabel("admin.jvm.day"));
        }
        p = p % (24 * 3600000);
        long hour = p / (3600000);
        if (hour > 0) {
            str.append(hour).append(Labels.getLabel("admin.jvm.hour"));
        }
        p = p % (3600000);
        long minute = p / (60000);
        if (minute > 0) {
            str.append(minute).append(Labels.getLabel("admin.jvm.minute"));
        }
        p = p % (60000);
        long second = p / (1000);
        if (second > 0) {
            str.append(second).append(Labels.getLabel("admin.jvm.second"));
        }
        p = p % (1000);
        if (p > 0 && p < 1000)
            str.append(p).append(Labels.getLabel("admin.jvm.millisecond"));

        return str.toString();
    }

    public final static String formatBytes(long size) {
        if (size < 0)
            return "N/A";
        if (size < 1024) {
            return size + "B";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            return size + "KB";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            size = size * 100;
            return (size / 100) + "."
                    + (size % 100) + "MB";
        } else {
            size = size * 100 / 1024;
            return (size / 100) + "."
                    + (size % 100) + "GB";
        }
    }
}
